﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace LINQToolKit.Query.Rules
{
    /// <summary>
    /// Represents the translator that performs by defined rules
    /// </summary>
    public class RuleTranslator : TranslatorBase
    {
        private QueryRule _rule;

        public RuleTranslator()
            : this(QueryRule.Default)
        {

        }

        public RuleTranslator(QueryRule rule)
        {
            rule.AssertNull("rule");
            this._rule = rule;
        }

        protected QueryRule Rule
        {
            get { return this._rule; }
        }

        protected override TranslatorBase CreateSubTranslator()
        {
            return new RuleTranslator(this.Rule);
        }

        protected override IEqualityComparer<string> GetQueryComparer()
        {
            return new UriEqualityComparer();
        }

        /// <summary>
        /// Translate the given expression to one query
        /// </summary>
        /// <param name="entityType"></param>
        /// <param name="condition"></param>
        /// <returns></returns>
        protected override string TranslateQuery(Type entityType, Expression condition)
        {
            if (condition.NodeType == ExpressionType.Call)
            {
                // translate the method rule
                var expr = (MethodCallExpression)condition;

                if (expr.Method.DeclaringType.IsAssignableFrom(entityType))
                {
                    return this.TranslateMethodByRule(entityType, expr);
                }
            }
            else
            {
                // process the binary expression
                // tranlate the member access by the property rule
                var expr = (BinaryExpression)condition;

                if (expr.Left.NodeType == ExpressionType.MemberAccess &&
                    expr.Right.NodeType == ExpressionType.Constant)
                {
                    return this.TranslateMemberAccesByRule(
                        entityType, (MemberExpression)expr.Left, (ConstantExpression)expr.Right, expr.NodeType);
                }
                else if (expr.Right.NodeType == ExpressionType.MemberAccess &&
                    expr.Left.NodeType == ExpressionType.Constant)
                {
                    return this.TranslateMemberAccesByRule(
                        entityType, (MemberExpression)expr.Right, (ConstantExpression)expr.Left, expr.NodeType);
                }
            }

            return null;
        }

        /// <summary>
        /// Translate the member access by the defined member rule
        /// </summary>
        /// <param name="entityType"></param>
        /// <param name="expr"></param>
        /// <param name="value"></param>
        /// <param name="oper"></param>
        /// <returns></returns>
        protected virtual string TranslateMemberAccesByRule(
            Type entityType, MemberExpression expr, ConstantExpression value, ExpressionType oper)
        {
            var memberName = expr.Member.Name;

            if (expr.Expression is MemberExpression)
            {
                var cur = (MemberExpression)expr.Expression;

                // loop to get the member name
                while (cur != null)
                {
                    memberName = cur.Member.Name + "." + memberName;
                    cur = cur.Expression as MemberExpression;
                }
            }

            var memberRule = this.Rule.GetPropertyRule(
                entityType, memberName, this.GetOperator(oper));

            if (memberRule == null)
            {
                return this.TranslateUndefinedMemberAccess(entityType, expr, value, oper);
            }

            return string.Format(memberRule.QueryUri, value.Value);
        }

        /// <summary>
        /// Translate the member access that undefined in rules
        /// </summary>
        /// <remarks>
        /// The derived class should implement this method to provide the query for undefined members
        /// </remarks>
        /// <param name="entityType"></param>
        /// <param name="expr"></param>
        /// <param name="value"></param>
        /// <param name="oper"></param>
        /// <returns></returns>
        protected virtual string TranslateUndefinedMemberAccess(
            Type entityType, MemberExpression expr, ConstantExpression value, ExpressionType oper)
        {
            return string.Empty;
        }

        /// <summary>
        /// Translate the method expression by defined method rule
        /// </summary>
        /// <param name="entityType"></param>
        /// <param name="expr"></param>
        /// <returns></returns>
        protected virtual string TranslateMethodByRule(Type entityType, MethodCallExpression expr)
        {
            var memberRule = this.Rule.GetMethodRule(entityType, expr.Method.Name);

            if (memberRule == null)
            {
                return this.TranslateUndefinedMethod(entityType, expr);
            }

            var args =
                from a in expr.Arguments
                where a.NodeType == ExpressionType.Constant
                select ((ConstantExpression)a).Value;

            return string.Format(memberRule.QueryUri, args.ToArray<object>());
        }

        /// <summary>
        /// Translate query for the undefined method
        /// </summary>
        /// <remarks>
        /// The derived class should implement this method to provide the query for undefined methods
        /// </remarks>
        /// <param name="entityType"></param>
        /// <param name="expr"></param>
        /// <returns></returns>
        protected virtual string TranslateUndefinedMethod(Type entityType, MethodCallExpression expr)
        {
            return string.Empty;
        }

        private string GetOperator(ExpressionType expressionType)
        {
            switch (expressionType)
            {
                case ExpressionType.Equal: { return "="; }
                case ExpressionType.NotEqual: { return "!="; }
                case ExpressionType.GreaterThan: { return ">"; }
                case ExpressionType.GreaterThanOrEqual: { return ">="; }
                case ExpressionType.LessThan: { return "<"; }
                case ExpressionType.LessThanOrEqual: { return "<="; }
                default:
                    {
                        throw new NotSupportedException("unkown expression type");
                    }
            }
        }

        protected override string TranslateUnkownQueryMethod(MethodCallExpression m)
        {
            return string.Empty;
        }

        protected override bool IsQueryMethod(MethodInfo methodInfo)
        {
            return true;
        }

        protected override string TranslateQueryString(MethodCallExpression m)
        {
            return string.Empty;
        }
    }
}
